<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebRTC P2P Demo</title>
    <script src="https://cdn.tailwindcss.com"></script>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css" rel="stylesheet">
    <script>
        tailwind.config = {
            theme: {
                extend: {
                    colors: {
                        primary: '#3B82F6',
                        secondary: '#10B981',
                        neutral: '#1F2937',
                    },
                    fontFamily: {
                        inter: ['Inter', 'sans-serif'],
                    },
                }
            }
        }
    </script>
    <style type="text/tailwindcss">
        @layer utilities {
            .content-auto {
                content-visibility: auto;
            }
            .text-shadow {
                text-shadow: 0 2px 4px rgba(0,0,0,0.1);
            }
            .transition-all-300 {
                transition: all 300ms ease-in-out;
            }
            .bg-gradient-blue {
                background: linear-gradient(135deg, #3B82F6 0%, #2563EB 100%);
            }
        }
    </style>
</head>
<body class="font-inter bg-gray-50 text-neutral min-h-screen flex flex-col">
    <header class="bg-gradient-blue text-white shadow-lg">
        <div class="container mx-auto px-4 py-6">
            <h1 class="text-[clamp(1.8rem,5vw,2.5rem)] font-bold text-shadow flex items-center">
                <i class="fa-solid fa-comments mr-3"></i>
                WebRTC P2P Demo
            </h1>
            <p class="text-blue-100 mt-2 max-w-2xl">
                实时点对点通信示例 - 在两个浏览器窗口之间发送消息
            </p>
        </div>
    </header>

    <main class="flex-grow container mx-auto px-4 py-8">
        <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
            <!-- 本地端 -->
            <div class="bg-white rounded-xl shadow-xl overflow-hidden transform hover:scale-[1.01] transition-all-300">
                <div class="bg-primary/10 px-6 py-4 border-b border-primary/20">
                    <h2 class="text-xl font-bold text-primary flex items-center">
                        <i class="fa-solid fa-user-circle mr-2"></i>
                        本地端
                    </h2>
                </div>
                <div class="p-6">
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-700 mb-1">我的ID</label>
                        <div class="flex">
                            <input type="text" id="localId" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none" readonly>
                            <button id="generateIdBtn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-r-lg transition-all-300">
                                <i class="fa-solid fa-refresh mr-1"></i> 生成ID
                            </button>
                        </div>
                    </div>

                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-700 mb-1">连接到</label>
                        <div class="flex">
                            <input type="text" id="remoteId" placeholder="输入对方ID" class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none">
                            <button id="connectBtn" class="bg-secondary hover:bg-secondary/90 text-white px-4 py-2 rounded-r-lg transition-all-300">
                                <i class="fa-solid fa-link mr-1"></i> 连接
                            </button>
                        </div>
                    </div>

                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-700 mb-1">消息</label>
                        <div class="border border-gray-300 rounded-lg h-48 overflow-y-auto p-3 bg-gray-50" id="messageDisplay">
                            <p class="text-gray-500 italic">等待连接...</p>
                        </div>
                    </div>

                    <div class="flex">
                        <input type="text" id="messageInput" placeholder="输入消息..." class="flex-1 px-4 py-2 border border-gray-300 rounded-l-lg focus:ring-2 focus:ring-primary focus:border-primary outline-none">
                        <button id="sendBtn" class="bg-primary hover:bg-primary/90 text-white px-4 py-2 rounded-r-lg transition-all-300" disabled>
                            <i class="fa-solid fa-paper-plane mr-1"></i> 发送
                        </button>
                    </div>
                </div>
            </div>

            <!-- 信令状态 -->
            <div class="bg-white rounded-xl shadow-xl overflow-hidden transform hover:scale-[1.01] transition-all-300">
                <div class="bg-primary/10 px-6 py-4 border-b border-primary/20">
                    <h2 class="text-xl font-bold text-primary flex items-center">
                        <i class="fa-solid fa-server mr-2"></i>
                        信令状态
                    </h2>
                </div>
                <div class="p-6">
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-700 mb-1">连接状态</label>
                        <div class="flex items-center">
                            <span id="connectionStatus" class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
                                <i class="fa-solid fa-circle mr-1 animate-pulse"></i> 未连接
                            </span>
                        </div>
                    </div>

                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-700 mb-1">信令服务器</label>
                        <div class="bg-gray-50 p-3 rounded-lg border border-gray-200">
                            <p class="text-sm"><span class="font-semibold">WebSocket:</span> <span id="wsStatus">连接中...</span></p>
                            <p class="text-sm mt-1"><span class="font-semibold">STUN:</span> stun:stun.l.google.com:19302</p>
                        </div>
                    </div>

                    <div>
                        <label class="block text-sm font-medium text-gray-700 mb-1">信令日志</label>
                        <div class="border border-gray-300 rounded-lg h-48 overflow-y-auto p-3 bg-gray-50" id="signalingLog">
                            <p class="text-gray-500 italic">等待信令...</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </main>

    <footer class="bg-neutral text-white py-6 mt-8">
        <div class="container mx-auto px-4">
            <div class="flex flex-col md:flex-row justify-between items-center">
                <div class="mb-4 md:mb-0">
                    <p class="text-gray-300">WebRTC P2P Demo &copy; 2025</p>
                </div>
                <div class="flex space-x-4">
                    <a href="#" class="text-gray-300 hover:text-white transition-colors">
                        <i class="fa-brands fa-github"></i>
                    </a>
                    <a href="#" class="text-gray-300 hover:text-white transition-colors">
                        <i class="fa-brands fa-twitter"></i>
                    </a>
                    <a href="#" class="text-gray-300 hover:text-white transition-colors">
                        <i class="fa-brands fa-linkedin"></i>
                    </a>
                </div>
            </div>
        </div>
    </footer>

    <script>
        // WebSocket信令服务器地址
        const SIGNALING_SERVER = 'ws://192.168.2.155:8080';
        // 本地和远程ID
        let localId = '';
        let remoteId = '';
        // WebRTC连接对象
        let peerConnection = null;
        // 数据通道
        let dataChannel = null;
        // WebSocket连接
        let ws = null;

        // DOM元素
        const localIdEl = document.getElementById('localId');
        const remoteIdEl = document.getElementById('remoteId');
        const generateIdBtn = document.getElementById('generateIdBtn');
        const connectBtn = document.getElementById('connectBtn');
        const messageInput = document.getElementById('messageInput');
        const messageDisplay = document.getElementById('messageDisplay');
        const sendBtn = document.getElementById('sendBtn');
        const connectionStatus = document.getElementById('connectionStatus');
        const wsStatus = document.getElementById('wsStatus');
        const signalingLog = document.getElementById('signalingLog');

        // 初始化
        function init() {
            // 生成随机ID
            generateIdBtn.addEventListener('click', generateRandomId);
            // 连接按钮点击事件
            connectBtn.addEventListener('click', connectToPeer);
            // 发送消息按钮点击事件
            sendBtn.addEventListener('click', sendMessage);
            // 消息输入框回车键发送
            messageInput.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    sendMessage();
                }
            });

            // 生成初始ID
            generateRandomId();
            // 连接到信令服务器
            connectToSignalingServer();
        }

        // 生成随机ID
        function generateRandomId() {
            localId = Math.random().toString(36).substring(2, 10);
            localIdEl.value = localId;
            log('生成本地ID: ' + localId);
        }

        // 连接到信令服务器
        function connectToSignalingServer() {
            ws = new WebSocket(SIGNALING_SERVER);
            
            ws.onopen = () => {
                log('WebSocket连接已建立');
                wsStatus.textContent = '已连接';
                wsStatus.className = 'text-green-600';
                
                // 注册本地ID
                sendToSignalingServer({
                    type: 'register',
                    id: localId
                });
            };
            
            ws.onmessage = (event) => {
                const message = JSON.parse(event.data);
                log('收到信令消息: ' + message.type);
                
                switch (message.type) {
                    case 'offer':
                        handleOffer(message);
                        break;
                    case 'answer':
                        handleAnswer(message);
                        break;
                    case 'candidate':
                        handleCandidate(message);
                        break;
                    case 'register-success':
                        log('本地ID注册成功');
                        updateConnectionStatus('等待连接', 'yellow');
                        break;
                    case 'error':
                        log('信令错误: ' + message.error, 'error');
                        break;
                    default:
                        log('未知消息类型: ' + message.type);
                }
            };
            
            ws.onclose = () => {
                log('WebSocket连接已关闭');
                wsStatus.textContent = '已断开';
                wsStatus.className = 'text-red-600';
                updateConnectionStatus('未连接', 'red');
                // 3秒后尝试重连
                setTimeout(connectToSignalingServer, 3000);
            };
            
            ws.onerror = (error) => {
                log('WebSocket错误: ' + error, 'error');
            };
        }

        // 发送消息到信令服务器
        function sendToSignalingServer(message) {
            if (ws && ws.readyState === WebSocket.OPEN) {
                ws.send(JSON.stringify(message));
                log('发送信令消息: ' + message.type);
            } else {
                log('WebSocket未连接，无法发送消息', 'error');
            }
        }

        // 连接到对等方
        function connectToPeer() {
            remoteId = remoteIdEl.value.trim();
            if (!remoteId || remoteId === localId) {
                log('请输入有效的远程ID', 'error');
                return;
            }
            
            // 创建RTCPeerConnection
            createPeerConnection();
            
            // 创建数据通道
            dataChannel = peerConnection.createDataChannel('chat');
            setupDataChannel(dataChannel);
            
            // 创建并发送offer
            peerConnection.createOffer()
                .then(offer => {
                    peerConnection.setLocalDescription(offer);
                    sendToSignalingServer({
                        type: 'offer',
                        from: localId,
                        to: remoteId,
                        offer: offer
                    });
                    updateConnectionStatus('正在连接...', 'yellow');
                })
                .catch(error => {
                    log('创建offer失败: ' + error, 'error');
                });
        }

        // 创建RTCPeerConnection
        function createPeerConnection() {
            const configuration = {
                iceServers: [
                    { urls: 'stun:stun.l.google.com:19302' }
                ]
            };
            
            peerConnection = new RTCPeerConnection(configuration);
            
            // ICE候选事件
            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    sendToSignalingServer({
                        type: 'candidate',
                        from: localId,
                        to: remoteId,
                        candidate: event.candidate
                    });
                }
            };
            
            // 连接状态变化事件
            peerConnection.onconnectionstatechange = () => {
                const state = peerConnection.connectionState;
                log('连接状态变化: ' + state);
                
                switch (state) {
                    case 'connected':
                        updateConnectionStatus('已连接', 'green');
                        sendBtn.disabled = false;
                        addMessage('系统', '连接已建立，可以开始聊天了！', 'system');
                        break;
                    case 'disconnected':
                    case 'failed':
                        updateConnectionStatus('连接断开', 'red');
                        sendBtn.disabled = true;
                        addMessage('系统', '连接已断开', 'system');
                        resetConnection();
                        break;
                    case 'closed':
                        updateConnectionStatus('连接已关闭', 'red');
                        sendBtn.disabled = true;
                        resetConnection();
                        break;
                }
            };
            
            // 接收数据通道事件
            peerConnection.ondatachannel = (event) => {
                log('接收到数据通道');
                dataChannel = event.channel;
                setupDataChannel(dataChannel);
            };
        }

        // 设置数据通道
        function setupDataChannel(channel) {
            channel.onopen = () => {
                log('数据通道已打开');
                updateConnectionStatus('已连接', 'green');
                sendBtn.disabled = false;
                addMessage('系统', '连接已建立，可以开始聊天了！', 'system');
            };
            
            channel.onclose = () => {
                log('数据通道已关闭');
                updateConnectionStatus('连接断开', 'red');
                sendBtn.disabled = true;
                addMessage('系统', '连接已断开', 'system');
                resetConnection();
            };
            
            channel.onmessage = (event) => {
                log('收到消息: ' + event.data);
                addMessage('对方', event.data, 'received');
            };
        }

        // 处理offer
        function handleOffer(message) {
            remoteId = message.from;
            createPeerConnection();
            
            peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer))
                .then(() => {
                    log('设置远程描述(offer)成功');
                    // 创建并发送answer
                    return peerConnection.createAnswer();
                })
                .then(answer => {
                    peerConnection.setLocalDescription(answer);
                    sendToSignalingServer({
                        type: 'answer',
                        from: localId,
                        to: remoteId,
                        answer: answer
                    });
                    updateConnectionStatus('正在连接...', 'yellow');
                })
                .catch(error => {
                    log('处理offer失败: ' + error, 'error');
                });
        }

        // 处理answer
        function handleAnswer(message) {
            peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer))
                .then(() => {
                    log('设置远程描述(answer)成功');
                })
                .catch(error => {
                    log('处理answer失败: ' + error, 'error');
                });
        }

        // 处理ICE候选
        function handleCandidate(message) {
            const candidate = new RTCIceCandidate(message.candidate);
            peerConnection.addIceCandidate(candidate)
                .then(() => {
                    log('添加ICE候选成功');
                })
                .catch(error => {
                    log('添加ICE候选失败: ' + error, 'error');
                });
        }

        // 发送消息
        function sendMessage() {
            const message = messageInput.value.trim();
            if (!message) return;
            
            if (dataChannel && dataChannel.readyState === 'open') {
                dataChannel.send(message);
                addMessage('我', message, 'sent');
                messageInput.value = '';
                messageInput.focus();
            } else {
                log('数据通道未打开，无法发送消息', 'error');
            }
        }

        // 添加消息到显示区域
        function addMessage(sender, message, type) {
            const time = new Date().toLocaleTimeString();
            let messageHTML = '';
            
            if (type === 'system') {
                messageHTML = `
                    <div class="flex justify-center my-2">
                        <span class="text-xs text-gray-500 bg-gray-100 px-2 py-1 rounded-full">${time}</span>
                    </div>
                    <div class="flex justify-center my-1">
                        <span class="text-xs text-gray-500 italic">${message}</span>
                    </div>
                `;
            } else if (type === 'sent') {
                messageHTML = `
                    <div class="flex justify-end my-2">
                        <div class="max-w-[80%] bg-primary text-white rounded-lg rounded-tr-none p-3 shadow">
                            <p class="text-sm">${message}</p>
                            <p class="text-xs text-blue-100 mt-1 text-right">${time}</p>
                        </div>
                    </div>
                `;
            } else {
                messageHTML = `
                    <div class="flex justify-start my-2">
                        <div class="max-w-[80%] bg-gray-200 text-gray-800 rounded-lg rounded-tl-none p-3 shadow">
                            <p class="text-sm">${message}</p>
                            <p class="text-xs text-gray-500 mt-1 text-right">${time}</p>
                        </div>
                    </div>
                `;
            }
            
            messageDisplay.insertAdjacentHTML('beforeend', messageHTML);
            messageDisplay.scrollTop = messageDisplay.scrollHeight;
        }

        // 更新连接状态
        function updateConnectionStatus(text, color) {
            connectionStatus.innerHTML = `<i class="fa-solid fa-circle mr-1"></i> ${text}`;
            
            switch (color) {
                case 'green':
                    connectionStatus.className = 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-green-100 text-green-800';
                    break;
                case 'yellow':
                    connectionStatus.className = 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800';
                    break;
                case 'red':
                    connectionStatus.className = 'inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-red-100 text-red-800';
                    break;
            }
        }

        // 重置连接
        function resetConnection() {
            if (dataChannel) {
                dataChannel.close();
                dataChannel = null;
            }
            
            if (peerConnection) {
                peerConnection.close();
                peerConnection = null;
            }
            
            remoteId = '';
            remoteIdEl.value = '';
        }

        // 记录日志
        function log(message, type = 'info') {
            const time = new Date().toLocaleTimeString();
            let logEntry = `<p class="text-sm ${type === 'error' ? 'text-red-600' : 'text-gray-600'}">${time} - ${message}</p>`;
            
            signalingLog.insertAdjacentHTML('beforeend', logEntry);
            signalingLog.scrollTop = signalingLog.scrollHeight;
        }

        // 页面加载完成后初始化
        window.addEventListener('load', init);
    </script>
</body>
</html>
    